Java 11 has been released, and many people are still using Java 8. This tutorial covers some important language features and APIs.
Local variables are those declared within the method body. Java 10 introduced a new keyword var
to replace type declarations when declaring local variables.
Before Java 10, you had to declare a variable like this:
String text = "Hello Java 9";
Now you can use var
instead of String
. The compiler will automatically infer the correct type from the variable’s assignment. For instance, the type of text
is inferred as String
:
var text = "Hello Java 10";
Variables declared with var
are still static and cannot be assigned a different type after declaration:
var text = "Hello Java11";
text = 23; // Compilation error: incompatible types
You can also use final
to declare constants:
final var text = "Banana";
text = "Joe"; // Compilation error
However, var
cannot be used when the compiler cannot infer the type, such as in these cases:
var a;
var nothing = null;
var lambda = () -> System.out.println("Pity!");
var method = this::someMethod;
When variable declarations involve generics, var
proves particularly advantageous. The following example uses var
to replace the verbose Map<String, List<Integer>>
:
var myList = new ArrayList<Map<String, List<Integer>>>();
for (var current : myList) {
// The type of 'current' is inferred as Map<String, List<Integer>>
System.out.println(current);
}
The var
keyword in Java 11 also supports use in lambda expression parameters, and you can add annotations to these parameters:
Predicate<String> predicate = (@Nullable var a) -> true;
Tip: In Intellij IDEA, hold down the CTRL key to see the inferred type of a variable.
Java 9 introduced a new API, HttpClient, for handling HTTP requests experimentally. Now, Java 11 standardizes it, and it can be accessed from the java.net
module.
The new HttpClient can be used in both synchronous and asynchronous scenarios. Synchronous requests block the thread until a response is received. BodyHandlers define the type of response data (e.g., String
, Byte[]
, File
).
var request = HttpRequest.newBuilder()
.uri(URI.create("https://blog.smallyu.net"))
.GET()
.build();
var client = HttpClient.newHttpClient();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
// Remember to import the java.net.http module in module-info.java
You can also make asynchronous requests. Calling sendAsync
will not block the current thread; it constructs an asynchronous operation chain and executes the corresponding action upon receiving a response:
var request = HttpRequest.newBuilder()
.uri(URI.create("https://blog.smallyu.net"))
.build();
var client = HttpClient.newHttpClient();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println);
// Sleep the thread to prevent it from ending before receiving the response
Thread.sleep(3000);
The
.GET()
method is the default request method.
The next example demonstrates how to send a request using the POST method to a specified URL. Similar to BodyHandlers
, BodyPublishers
defines the type of data to be sent:
var request = HttpRequest.newBuilder()
.uri(URI.create("https://postman-echo.com/post"))
.header("Content-Type", "text/plain")
.POST(HttpRequest.BodyPublishers.ofString("Hi there!"))
.build();
var client = HttpClient.newHttpClient();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode()); // 200
The last example shows how to perform authentication using BASIC-AUTH:
var request = HttpRequest.newBuilder()
.uri(URI.create("https://postman-echo.com/basic-auth"))
.build();
var client = HttpClient.newBuilder()
.authenticator(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("postman", "password".toCharArray());
}
})
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode()); // 200
Collections frameworks like List, Set, and Map have new methods added. List.of
creates an immutable list from the given parameters, and List.copyOf
creates a copy of an existing list.
var list = List.of("A", "B", "C");
var copy = List.copyOf(list);
System.out.println(list == copy); // true
Since the list is immutable, the copied list and the original list are the same instance. If a mutable list is copied, the copied list will be a new instance without affecting the original list:
var list = new ArrayList<String>();
var copy = List.copyOf(list);
System.out.println(list == copy); // false
To create an immutable map, simply alternate the keys and values as parameters:
var map = Map.of("A", 1, "B", 2);
System.out.println(map); // {B=2, A=1}
The immutable lists in Java 11 use the same interface as lists in older versions, but modifying an immutable list (e.g., adding or removing elements) will throw a
java.lang.UnsupportedOperationException
. Fortunately, when you try to modify an immutable list, Intellij IDEA will check and warn you.
Streams, introduced in Java 8, now have three new methods. Stream.ofNullable
creates a stream from a single element:
Stream.ofNullable(null)
.count() // 0
dropWhile
and takeWhile
methods are used to discard some elements in the stream:
Stream.of(1, 2, 3, 2, 1)
.dropWhile(n -> n < 3)
.collect(Collectors.toList()); // [3, 2, 1]
Stream.of(1, 2, 3, 2, 1)
.takeWhile(n -> n < 3)
.collect(Collectors.toList()); // [1, 2]
The String
class also has several new methods:
" ".isBlank(); // true
" Foo Bar ".strip(); // "Foo Bar"
" Foo Bar ".stripTrailing(); // " Foo Bar"
" Foo Bar ".stripLeading(); // "Foo Bar "
"Java".repeat(3); // "JavaJavaJava"
"A\nB\nC".lines().count(); // 3
Java 11 includes many new features; the above are just the tip of the iceberg. There is much more to explore…